package com.harmontronics.hanma.paas.paas.domain.app.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import com.harmontronics.hanma.paas.paas.common.PageBase;
import com.harmontronics.hanma.paas.paas.consts.DbConstants;
import com.harmontronics.hanma.paas.paas.domain.app.entity.HmModel;
import com.harmontronics.hanma.paas.paas.domain.app.entity.HmPage;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.Fields;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.I18nKeyEnum;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.ModelTypeEnum;
import com.harmontronics.hanma.paas.paas.domain.app.repository.persistence.HmEntityModelRepository;
import com.harmontronics.hanma.paas.paas.domain.app.repository.persistence.HmModelRepository;
import com.harmontronics.hanma.paas.paas.domain.app.repository.persistence.HmPageRepository;
import com.harmontronics.hanma.paas.paas.domain.app.repository.po.HmEntityModelPO;
import com.harmontronics.hanma.paas.paas.domain.app.repository.po.HmModelPO;
import com.harmontronics.hanma.paas.paas.domain.app.service.dialect.AbstractDialectService;
import com.harmontronics.hanma.paas.paas.domain.app.service.dialect.DialectDecision;
import com.harmontronics.hanma.paas.paas.domain.app.service.dialect.ViewColumn;
import com.harmontronics.hanma.paas.paas.exception.ErrorKeyException;
import com.harmontronics.hanma.paas.paas.token.ComposeTokenKit;
import com.harmontronics.hanma.paas.paas.utils.EscapeUtil;
import com.harmontronics.hanma.paas.paas.utils.StringUtil;
import com.jayway.jsonpath.JsonPath;
import java.util.HashMap;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author ldb
 */
@Service("hmModelService")
@DS("#header.App-Unique")
public class HmModelService {

    @Autowired
    HmHttpRequestDefinitionService hmHttpRequestDefinitionService;
    @Autowired
    private HmModelRepository hmModelRepository;
    @Autowired
    private HmPageRepository hmPageRepository;
    @Autowired
    private HmPageService hmPageService;
    @Autowired
    private HmEntityModelService hmEntityModelService;
    @Autowired
    private HmEntityModelRepository hmEntityModelRepository;
    @Autowired
    private ComposeTokenKit composeTokenKit;
    @Autowired
    private DialectDecision dialectDecision;

    public HmModel findById(Long id) {
        HmModelPO po = hmModelRepository.getById(id);
        required(po);
        return BeanUtil.toBean(po, HmModel.class);
    }

    public HmModel findByCode(String code) {
        HmModelPO po = hmModelRepository.findByCode(code);
        required(po);
        return BeanUtil.toBean(po, HmModel.class);
    }

    @Transactional(rollbackFor = Exception.class)
    public Long save(HmModel hmModel) {
        uniqueCreate(hmModel);
        HmModelPO po = BeanUtil.toBean(hmModel, HmModelPO.class);
        hmModelRepository.save(po);
        Boolean supportProcess = hmModel.getSupportProcess();
        Boolean supportTree = hmModel.getSupportTree();
        if (hmModel.getType() == ModelTypeEnum.ENTITY) {
            if (supportProcess != null && supportProcess) {
                hmModelRepository.createTableWoProcess(po.getCode());
            } else if (supportTree && supportTree) {
                hmModelRepository.createTableWoTree(po.getCode());
            } else {
                hmModelRepository.createTable(po.getCode());
            }
            hmEntityModelService.saveDefaultEntityModel(po.getId(), supportProcess, supportTree,true);
            hmHttpRequestDefinitionService.addDefaultForEntityModel(po.getId(), false, hmModel.getSupportTree() != null && hmModel.getSupportTree());
        } else if (hmModel.getType() == ModelTypeEnum.VIEW) {
            hmHttpRequestDefinitionService.addDefaultForEntityModel(po.getId(), true, false);
        } else if (hmModel.getType() == ModelTypeEnum.VIRTUAL) {
            // 默认添加 id字段，如果是树结构，则默认添加parent_id、parent_ids、node_name和children字段
            // parent_id字段的显示规则默认使用${node_name}，如果修改node_name名称，则同步修改parent_id的显示规则
            hmEntityModelService.saveDefaultEntityModel(po.getId(), supportProcess, supportTree,false);
        }
        return po.getId();
    }

    private void required(HmModelPO po) {
        if (po == null) {
            throw new ErrorKeyException(I18nKeyEnum.MODEL_NOT_FOUND);
        }
    }

    public void uniqueCreate(HmModel model) {
        if (hmModelRepository.findByCode(model.getCode()) != null) {
            throw new ErrorKeyException(I18nKeyEnum.ENTITY_MODEL_CODE_EXISTS);
        }
        if (hmModelRepository.findByName(model.getName()) != null) {
            throw new ErrorKeyException(I18nKeyEnum.ENTITY_MODEL_NAME_EXISTS);
        }
    }


    public void update(HmModel hmModel) {
        uniqueUpdate(hmModel);
        HmModelPO po = hmModelRepository.getById(hmModel.getId());
        po.setName(hmModel.getName());
        po.setDescription(hmModel.getDescription());
        if (po.getType() == ModelTypeEnum.VIEW) {
            po.setDatasourceId(hmModel.getDatasourceId());
            po.setViewName(hmModel.getViewName());
        }
        if (po.getType() == ModelTypeEnum.VIRTUAL) {
            po.setHttpServiceId(hmModel.getHttpServiceId());
        }
        hmModelRepository.updateById(po);
    }

    public void uniqueUpdate(HmModel model) {
        HmModelPO po = hmModelRepository.findByName(model.getName());
        if (po == null) {
            return;
        }
        if (po.getId().longValue() != model.getId().longValue()) {
            throw new ErrorKeyException(I18nKeyEnum.ENTITY_MODEL_NAME_EXISTS);
        }
    }


    @Transactional(rollbackFor = Exception.class)
    public List<String> remove(List<Long> ids) {
        List<String> codeList = new ArrayList<>();
        for (Long id : ids) {
            int pageCount = hmPageRepository.countByModelId(id);
            if (pageCount > 0) {
                throw new ErrorKeyException(I18nKeyEnum.MODEL_NOT_ALLOW_DELETE);
            }
            HmModelPO po = hmModelRepository.getById(id);
            if (po.getType() == ModelTypeEnum.ENTITY) {
                hmModelRepository.dropTable(po.getCode());
                codeList.add(po.getCode());
            }
            hmModelRepository.removeById(id);
        }
        return codeList;
    }


    public List<HmModel> getList() {
        List<HmModelPO> pos = hmModelRepository.list();
        return Convert.toList(HmModel.class, pos);
    }


    public PageBase<HmModel> getList(String modelTypes, String name, int pageNo, int pageSize) {
        Page<HmModelPO> page = new Page<>(pageNo, pageSize);
        QueryWrapper<HmModelPO> queryWrapper = new QueryWrapper<>();
        if (StringUtils.hasLength(modelTypes)) {
            Set<String> typeSet = StringUtils.commaDelimitedListToSet(modelTypes);
            Set<ModelTypeEnum> typeEnums = typeSet.stream().map(ModelTypeEnum::valueOf).collect(
                    Collectors.toSet());
            queryWrapper.in(Fields.TYPE_DB, typeEnums);
        }
        if (!StringUtils.isEmpty(name)) {
            queryWrapper.like(Fields.NAME, EscapeUtil.escapeChar(name));
        }
        queryWrapper.orderByDesc(DbConstants.CREATE_DATE);
        IPage<HmModelPO> resultPage =
                hmModelRepository.selectPage(queryWrapper, page);
        return PageBase.from(resultPage, HmModel.class);
    }

    public void validField(Long id, String field, String value) {
        if (Fields.NAME.equals(field)) {
            HmModelPO po = hmModelRepository.findByName(value);
            if (po == null) {
                return;
            }
            if (id == null) {
                throw new ErrorKeyException(I18nKeyEnum.ENTITY_MODEL_NAME_EXISTS);
            }
            if (po.getId().longValue() != id.longValue()) {
                throw new ErrorKeyException(I18nKeyEnum.ENTITY_MODEL_NAME_EXISTS);
            }
        }

        if (Fields.CODE.equals(field)) {
            HmModelPO po = hmModelRepository.findByCode(value);
            if (po == null) {
                return;
            }
            if (id == null) {
                throw new ErrorKeyException(I18nKeyEnum.ENTITY_MODEL_CODE_EXISTS);
            }
            if (po.getId().longValue() != id.longValue()) {
                throw new ErrorKeyException(I18nKeyEnum.ENTITY_MODEL_CODE_EXISTS);
            }
        }
    }

    @Deprecated
    public void addData(Long pageId, Map<String, Object> data) {
        HmPage hmPage = hmPageService.findById(pageId, false);
        HmModelPO po = hmModelRepository.getById(hmPage.getModelId());
        required(po);
        putDefaultFields(data);
        hmModelRepository.dynamicInsert(po.getCode(), data);
    }

    @Transactional(rollbackFor = Exception.class)
    public void addData(String modelCode, Long modelId, Map<String, Object> data) {
        putDefaultFields(data);

        /**
            对于子表，需要同时保存子表数据，处理流程如下：
             Step1：检查模型（code）是否包含子表字段，并获取子表字段信息；
             Step2：从values中获取子表数据；
             Step3：保存主表记录；|
             Step4：保存子表记录
         */
        /**
         * 子表数据
         * <主表关联字段code,关联字段实体信息>
         */
        Map<String,Object> subTableData = Maps.newHashMap();
        // 关联字段map结构，作用：快速获取关联字段实体信息
        Map<String, HmEntityModelPO> subTableByModelCodeMap = new HashMap<>();

        // 初始化子表数据：查询关联子表的字段集合，提取子表数据（subTableData）
        makeSubTableInfo(modelId, data, subTableData, subTableByModelCodeMap);

        // 保存主表数据
        Long id = hmModelRepository.dynamicInsert(modelCode, data);

        // 保存子表信息
        saveOrUpdateRelationSubTableData(subTableData, subTableByModelCodeMap, id,false);
    }

    // 保存子表数据
    private void saveOrUpdateRelationSubTableData(Map<String, Object> subTableData,
            Map<String, HmEntityModelPO> subTableByModelCodeMap, Long parentTableId,boolean needClean) {
        if(!subTableData.isEmpty()){
            // 遍历子表数据
            for(String relationColumnCode: subTableData.keySet()){
                Object curData = subTableData.get(relationColumnCode);
                // 关联子表的字段实体
                HmEntityModelPO entityModel = subTableByModelCodeMap.get(relationColumnCode);
                if(StringUtil.isNotBlank(entityModel.getConfig())){
                    // 解析关联字段中的子表模型code和关联子表的字段code
                    String subTableModelCode = JsonPath.read(entityModel.getConfig(),"$.modelCode");
                    String relationSubTableColumnCode = JsonPath.read(entityModel.getConfig(),"$.value");

                    // 排除脏数据
                    if(StringUtil.isNotBlank(subTableModelCode)
                            && StringUtil.isNotBlank(relationSubTableColumnCode)){

                        // 校验子表数据结构是否为：List<Map<String,Object>>
                        if (!(curData instanceof List)
                                || (!((List) curData).isEmpty()
                                && ((List) curData).stream().anyMatch(item->!(item instanceof Map)))){
                            throw new ErrorKeyException(I18nKeyEnum.RELATION_SUB_TABLE_DATA_ERROR);
                        }

                        if(needClean){
                            // 清空子表数据
                            hmModelRepository.dynamicTruncateData(subTableModelCode);
                        }

                        // 解析子表数据，并存储
                        List curDataList = (List)curData;
                        if(!curDataList.isEmpty()){
                            // 存储子表字段名称，包含关联的外键
                            List<String> subTableColumnCodes = new ArrayList<>();
                            List<String> subTableColumnValuesParts = new ArrayList<>();
                            for(Object tempData : curDataList){
                                Map<String, Object> realTempData = (Map<String, Object>)tempData;
                                if(!realTempData.isEmpty()){
                                    // 注意：一定要移除子表中关联主模型的字段，以防误传
                                    realTempData.remove(relationSubTableColumnCode);
                                    // 移除主键，避免编辑时的新增报错
                                    realTempData.remove(DbConstants.ID);
                                    // 添加默认字段
                                    putDefaultFields(realTempData);
                                    // 遍历子表表单，添加值
                                    List<String> columnValues = new ArrayList<>();
                                    for(String curColumnKey : realTempData.keySet()){
                                        if(!subTableColumnCodes.contains(curColumnKey)){
                                            subTableColumnCodes.add(curColumnKey);
                                        }
                                        columnValues.add("'"+realTempData.get(curColumnKey).toString()+"'");
                                    }

                                    if(!subTableColumnCodes.contains(relationSubTableColumnCode)){
                                        subTableColumnCodes.add(relationSubTableColumnCode);
                                    }
                                    // 添加子表关联主表的字段
                                    columnValues.add("'"+ parentTableId.toString()+"'");
                                    // 构建 insert的valuePart
                                    String columnValuesPart = "("+String.join(",",columnValues)+")";
                                    subTableColumnValuesParts.add(columnValuesPart);
                                }
                            }
                            String inertKeysPart = String.join(",",subTableColumnCodes);
                            String inertValuesPart = String.join(",",subTableColumnValuesParts);
                            // 批量保存子表数据
                            hmModelRepository.dynamicInsertInBatch(subTableModelCode,inertKeysPart,inertValuesPart);
                        }
                    }
                }
            }
        }
    }

    // 初始化子表关联信息
    private void makeSubTableInfo(Long modelId, Map<String, Object> data,
            Map<String, Object> subTableData, Map<String, HmEntityModelPO> subTableByModelCodeMap) {
        List<HmEntityModelPO> subTableByModelCode =  hmEntityModelRepository.findSubTableColumns(modelId);
        if(CollectionUtil.isNotEmpty(subTableByModelCode)){
            subTableByModelCode.forEach(item->{
                subTableByModelCodeMap.put(item.getCode(),item);
                if(data.containsKey(item.getCode())){
                    subTableData.put(item.getCode(),data.remove(item.getCode()));
                }
            });
        }
    }

    public Map<String, Object> dynamicGet(String modelCode,Long modelId,Long id){
        HmModelPO hmModelPO = hmModelRepository.findByCode(modelCode);
        required(hmModelPO);
        Map<String, Object> result = hmModelRepository.dynamicGet(modelCode, id);
        if(result != null && !result.isEmpty()){
            // 检查该业务模型是否包含子表字段
            List<HmEntityModelPO> subTableByModelCode =  hmEntityModelRepository.findSubTableColumns(modelId);
            if(CollectionUtil.isNotEmpty(subTableByModelCode)){
                // 获取子表数据
                subTableByModelCode.forEach(item->{
                    // 从config中解析子表modelCode 和关联子表的字段code
                    String subTableModelCode = JsonPath.read(item.getConfig(),"$.modelCode");
                    String relationSubTableColumnCode = JsonPath.read(item.getConfig(),"$.value");

                    // 查询子表明细数据
                    List<Map<String, Object>> subTableDetails = hmModelRepository
                            .dynamicGetSubTableData(subTableModelCode, relationSubTableColumnCode,
                                    id);
                    result.put(item.getCode(),subTableDetails);
                });
            }
        }
        return result;
    }

    public HmModel findByPageId(Long pageId) {
        HmPage hmPage = hmPageService.findById(pageId, false);
        HmModelPO po = hmModelRepository.getById(hmPage.getModelId());
        required(po);
        return BeanUtil.toBean(po, HmModel.class);
    }

    private void putDefaultFields(Map<String, Object> data) {
        String username = composeTokenKit.getUserInfo().getUserName();
        data.put(DbConstants.CREATE_DATE, LocalDateTime.now());
        data.put(DbConstants.CREATE_BY, username);
        data.put(DbConstants.MODIFY_BY, username);
        data.put(DbConstants.MODIFY_DATE, LocalDateTime.now());
    }

    @Deprecated
    public void editData(Long pageId, Map<String, Object> data) {
        HmPage hmPage = hmPageService.findById(pageId, false);
        HmModelPO po = hmModelRepository.getById(hmPage.getModelId());
        required(po);
        String username = composeTokenKit.getUserInfo().getUserName();
        data.put(DbConstants.MODIFY_BY, username);
        data.put(DbConstants.MODIFY_DATE, LocalDateTime.now());
        hmModelRepository.dynamicUpdate(po.getCode(), data);
    }

    @Transactional(rollbackFor = Exception.class)
    public void editData(String modelCode, Long modelId,Map<String, Object> data) {
        String username = composeTokenKit.getUserInfo().getUserName();
        data.put(DbConstants.MODIFY_BY, username);
        data.put(DbConstants.MODIFY_DATE, LocalDateTime.now());

        // 提取子表数据
        Map<String,Object> subTableData = Maps.newHashMap();
        Map<String, HmEntityModelPO> subTableByModelCodeMap = new HashMap<>();

        // 初始化子表数据：查询关联的子表信息，抽取子表数据（subTableData）
        makeSubTableInfo(modelId, data, subTableData, subTableByModelCodeMap);

        // 更新子表数据，方法：先清空子表数据，再加新数据
        if(!subTableData.isEmpty()){
            // 主表记录id
            Long parentId = Long.parseLong(data.get(DbConstants.ID).toString());
            saveOrUpdateRelationSubTableData(subTableData, subTableByModelCodeMap, parentId,true);
        }

        // 更新主表的数据
        hmModelRepository.dynamicUpdate(modelCode, data);
    }

    @Deprecated
    public void deleteData(Long pageId, Long dataId) {
        HmPage hmPage = hmPageService.findById(pageId, false);
        HmModelPO po = hmModelRepository.getById(hmPage.getModelId());
        required(po);
        hmModelRepository.dynamicDelete(po.getCode(), dataId);
    }

    public void deleteData(String code, Long dataId) {
        hmModelRepository.dynamicDelete(code, dataId);
    }

    @Deprecated
    public PageBase<Map<String, Object>> listData(Long pageId, Map<String, Object> params, List<String> orderRules, Integer pageNo, Integer pageSize) throws SQLException {
        HmPage hmPage = hmPageService.findById(pageId, false);

        HmModelPO po = hmModelRepository.getById(hmPage.getModelId());
        required(po);
        //为空  不分页
        if (pageSize == null) {
            if (po.getType() == ModelTypeEnum.VIEW) {
                //     return dialectDecision.getService(po.getDatasourceId()).list(po,params,orderRules);
            }

            List<Map<String, Object>> dataList = hmModelRepository.dynamicList(po.getCode(), params, orderRules);
            PageBase<Map<String, Object>> pageBase = new PageBase<>();
            pageBase.setData(dataList);
            return pageBase;
        }

        if (po.getType() == ModelTypeEnum.VIEW) {
            return dialectDecision.getService(po.getDatasourceId()).pageList(po, params, orderRules, pageNo, pageSize);
        }
        IPage<Map<String, Object>> iPage = hmModelRepository.dynamicPageList(po.getCode(), params, orderRules, new Page(pageNo, pageSize));
        PageBase<Map<String, Object>> pageBase = new PageBase<>();
        pageBase.setPageNo(pageNo);
        pageBase.setPageSize(pageSize);
        pageBase.setData(iPage.getRecords());
        pageBase.setTotalPage(iPage.getPages());
        pageBase.setTotalCount(iPage.getTotal());
        return pageBase;
    }

    public Map<String, Object> getData(Long pageId, Long id) {
        HmPage hmPage = hmPageService.findById(pageId, false);
        HmModelPO po = hmModelRepository.getById(hmPage.getModelId());
        required(po);
        return hmModelRepository.dynamicGet(po.getCode(), id);
    }

    public List<ViewColumn> getViewColumns(Long modelId) {
        HmModelPO po = hmModelRepository.getById(modelId);
        required(po);
        if (po.getType() != ModelTypeEnum.VIEW) {
            throw new ErrorKeyException(I18nKeyEnum.ONLY_VIEW_TYPE_SUPPORTED);
        }
        AbstractDialectService dialectService = dialectDecision.getService(po.getDatasourceId());
        return dialectService.getViewColumns(po.getDatasourceId(), po.getViewName());
    }
}